`timescale 1ns/100ps

`include "sim_glb.sv"

module tc;
localparam          TEST_NUM                = 50000;

localparam          CLK_WR_PRD              = 4;
localparam          CLK_RD_PRD              = 5;

localparam          CDATA_BW                = 8;
localparam          CLEN_MAX                = 16;       // it's very important to set right value, otherwise wrong cell maximum length lead to dead state
localparam          MEM_DEPTH               = 512;      // actural maximum wr_used is MEM_DEPTH-1, it must be even if ASYNC_CLK==1'b1
localparam          WRAP_NUM_ADAE           = 0;        // the number of added data after EOC which will be wrapped to head of cell
localparam          ASYNC_CLK               = 1'b1;     // clock of read and write is 1'b0:synchronous; 1'b1:asynchronous
localparam          SYNC_NUM_W2R            = 2;
localparam          SYNC_NUM_R2W            = 2;
localparam          GRAY_ADDR_LPL           = 0;        // if RAPL=WAPL=0, let GRAY_ADDR_LPL=0, else 1
localparam          RDPL_NUM                = 2;        // read operation pipeline steps, include read address and data pipeline
localparam          ARCH_MODE               = "RDPL_SHIFT"; // the smallest area
//localparam          ARCH_MODE               = "RDPL_STUFF"; // no bubble exist, but memory must support individul CE for each RDPL step
//localparam          ARCH_MODE               = "RDPL_CACHE"; // no bubble exist, but more cache REGs comsumed, about (RDPL_NUM+2)*DATA_BW
localparam          ADDR_BW                 = $clog2(MEM_DEPTH);    // address range [0:MEM_DEPTH-1]

reg                                         rst_wr_n;
reg                                         clk_wr;

reg                                         rst_rd_n;
reg                                         clk_rd;

wire                                        wr_rdy;     // 1:local side is ready for taking cell data; 0:full
reg                                         wr_vld;     // 1:peer side is valid for offerring cell data; 0:empty
reg                 [CDATA_BW-1:0]          wr_data;    // both wr_vld and wr_rdy asserting means one successful transfering cell data
reg                                         wr_eoc;     // end of cell, valid when wr_vld==1'b1, don't care when others
reg                                         wr_drop;    // valid when wr_eoc==1'b1, don't care when others

reg                 [ADDR_BW-1:0]           cfg_afull_th;
wire                [ADDR_BW-1:0]           wr_cnt;
wire                                        wr_afull;
wire                                        wr_err;
reg                                         rd_rdy;     // 1:peer side is ready for taking cell data; 0:busy
wire                                        rd_vld;     // 1:local side is ready for offerring cell data; 0:empty
wire                [CDATA_BW-1:0]          rd_data;    // both rd_vld and rd_rdy asserting means one successful transfering  cell data
wire                                        rd_eoc;

wire                [ADDR_BW-1:0]           waddr2rd;
wire                [ADDR_BW-1:0]           raddr2wr;

wire                                        mem_wen;
wire                [ADDR_BW-1:0]           mem_waddr;
wire                [CDATA_BW+1-1:0]        mem_wdata;
wire                                        mem_ren;
wire                [RDPL_NUM-1:0]          mem_rdpl_ce;
wire                [ADDR_BW-1:0]           mem_raddr;
wire                [CDATA_BW+1-1:0]        mem_rdata;

integer TxCellNum, DropCellNum, RxCellNum;

initial begin:CRG
    rst_wr_n=1'b0;
    clk_wr=1'b0;
    rst_rd_n=1'b0;
    clk_rd=1'b0;

    fork
        rst_wr_n=#100.5 1'b1;
        forever clk_wr=#CLK_WR_PRD ~clk_wr;
        rst_rd_n=#100.5 1'b1;
        forever clk_rd=#CLK_RD_PRD ~clk_rd;
    join
end

RGRS_MNG    rgrs;
initial begin:REGRESS
    rgrs = new("tc_cfifo_ctrl", 2);

    rgrs.wait_chks_done(100_000_000);
end

initial begin:GEN_CELL
    integer gap, len;
    reg [CDATA_BW-1:0] ini;
    reg drop;

    wr_vld  = 1'b0;
    wr_eoc  = 1'b0;
    wr_drop = 1'b0;

    TxCellNum = 0;
    DropCellNum = 0;

    @(posedge rst_wr_n);
    @(posedge clk_wr);

    wr_cell(10, 16,   1, 1'b0);
    wr_cell(10, 17,  40, 1'b0);
    wr_cell(10, 18,  80, 1'b0);
    wr_cell(10, 19, 120, 1'b0);
    while(TxCellNum<TEST_NUM) begin
        gap = $urandom_range(0, 16);
        len = $urandom_range(WRAP_NUM_ADAE+1, CLEN_MAX);
        {ini, drop} = $urandom();
        wr_cell(gap, len, ini, drop);
        if (drop==1'b1)
            DropCellNum = DropCellNum+1;
        else
            TxCellNum = TxCellNum + 1;
    end
    rgrs.one_chk_done("gen is done.");
end

initial begin:CHK_CELL
    integer gap;

    rd_rdy = 1'b0;
    RxCellNum=0;

    @(posedge rst_rd_n);
    @(posedge clk_rd);

    while(RxCellNum<TEST_NUM)begin
        gap = $urandom_range(0, 50);
        rd_cell(gap);
        RxCellNum = RxCellNum+1;
    end
    #1000;
    rgrs.one_chk_done("chk is done.");
end

cfifo_ctrl #(
        .CDATA_BW                       (CDATA_BW                       ),	// cell data bit width
        .CLEN_MAX                       (CLEN_MAX                       ),	// the longest cell length <= CLEN_MAX < MEM_DEPTH - (the longest cell length)
        .MEM_DEPTH                      (MEM_DEPTH                      ),	// it must be even if ASYNC_CLK==1'b1, actural maximum wr_used is MEM_DEPTH-1
        .WRAP_NUM_ADAE                  (WRAP_NUM_ADAE                  ),	// the number of added data after EOC which will be wrapped to head of cell
        .ASYNC_CLK                      (ASYNC_CLK                      ),	// clock of read and write is 1'b0:synchronous; 1'b1:asynchronous
        .SYNC_NUM_W2R                   (SYNC_NUM_W2R                   ),
        .SYNC_NUM_R2W                   (SYNC_NUM_R2W                   ),
        .GRAY_ADDR_LPL                  (GRAY_ADDR_LPL                  ),	// if RAPL=WAPL=0, let GRAY_ADDR_LPL=0, else 1
        .RDPL_NUM                       (RDPL_NUM                       ),	// read operation pipeline steps, include read address and data pipeline
        .ARCH_MODE                      (ARCH_MODE                      ) 	// the smallest area
) u_cfifo_ctrl ( 
        .rst_wr_n                       (rst_wr_n                       ),
        .clk_wr                         (clk_wr                         ),

        .wr_rdy                         (wr_rdy                         ),	// 1:local side is ready for taking cell data; 0:full
        .wr_vld                         (wr_vld                         ),	// 1:peer side is valid for offerring cell data; 0:empty
        .wr_data                        (wr_data                        ),	// both wr_vld and wr_rdy asserting means one successful transfering cell data
        .wr_eoc                         (wr_eoc                         ),	// end of cell, valid when wr_vld, don't care when others
        .wr_drop                        (wr_drop                        ),	// valid when wr_eoc, don't care when others

// unused if WRAP_NUM_ADAE=0
        .wrap_rdy                       (                               ),
        .wrap_cnt                       (                               ),	// increase from 0 to WARP_NUM_ADAE-1
        .wrap_end                       (                               ),
        .wrap_data                      ({CDATA_BW{1'b0}}               ),
        .wrap_ofst                      (1'b0                           ),	// offset address for wrap_data
        .wrap_drop                      (1'b0                           ),	// high pulse after wr_eoc but before wrap_end(included) means to drop current cell

        .cfg_afull_th                   (cfg_afull_th                   ),
        .wr_cnt                         (wr_cnt                         ),	// 0 <
        .wr_afull                       (wr_afull                       ),	// wr_afull assert when wr_cnt>cfg_afull_th
        .wr_err                         (wr_err                         ),	// cell data is over length and droped

        .rst_rd_n                       (rst_rd_n                       ),
        .clk_rd                         (clk_rd                         ),

        .rd_rdy                         (rd_rdy                         ),	// 1:peer side is ready for taking cell data; 0:busy
        .rd_vld                         (rd_vld                         ),	// 1:local side is valid for offerring cell data; 0:empty
        .rd_data                        (rd_data                        ),	// both rd_vld and rd_rdy asserting means one successful transfering  cell data
        .rd_eoc                         (rd_eoc                         ),

        .mem_wen                        (mem_wen                        ),
        .mem_waddr                      (mem_waddr                      ),
        .mem_wdata                      (mem_wdata                      ),
        .mem_ren                        (mem_ren                        ),
        .mem_rdpl_ce                    (mem_rdpl_ce                    ),
        .mem_raddr                      (mem_raddr                      ),
        .mem_rdata                      (mem_rdata                      )
);

tpram_wrap #(
        .DEPTH                          (MEM_DEPTH                      ),
        .DATA_BW                        (CDATA_BW+1                     ),
        .RDPL_NUM                       (RDPL_NUM                       ),	// Read Data Pipe Line number
        .USER_DEF_TAG                   ("NONE"                         ),
        .ECC_MODE                       ("SEC_ONLY"                     ) 	// Single Error Correction only, not support Double Error Detection
) u_tpram ( 
        .rst_wr_n                       (rst_wr_n                       ),
        .clk_wr                         (clk_wr                         ),
        .mem_wen                        (mem_wen                        ),
        .mem_waddr                      (mem_waddr                      ),
        .mem_wdata                      (mem_wdata                      ),

        .cfg_frc_sbe                    (1'b0                           ),	// support at ECC_MODE

        .rst_rd_n                       (rst_rd_n                       ),
        .clk_rd                         (clk_rd                         ),
        .mem_ren                        (mem_ren                        ),
        .mem_rdpl_ce                    (mem_rdpl_ce                    ),
        .mem_raddr                      (mem_raddr                      ),
        .mem_rvld                       (                               ),
        .mem_rdata                      (mem_rdata                      ),

        .alm_ecc_err                    (                               ),	// support at ECC_MODE
        .alm_ecc_dbe                    (                               )	// support at ECC_MODE
);

task wr_cell;
    input integer           gap;
    input integer           len;
    input [CDATA_BW-1:0]    ini;
    input                   drop;

    integer i;
    begin
        repeat(gap) begin
            @(posedge clk_wr);
        end
        for(i=1; i<=len; i=i+1) begin
            wr_vld  <=`U_DLY 1'b1;
            wr_data <=`U_DLY (i==1)   ? len-1 :
                             (i==2)   ? ini   : wr_data+1;
            wr_eoc  <=`U_DLY (i==len) ? 1'b1 : 1'b0;
            wr_drop <=`U_DLY (i==len) ? drop : 1'b0;

            @(posedge clk_wr);
            while(wr_rdy==1'b0) begin
                @(posedge clk_wr);
            end
        end
        wr_vld  <=`U_DLY 1'b0;
        wr_eoc  <=`U_DLY 1'b0;
        wr_drop <=`U_DLY 1'b0;
    end
endtask

task rd_cell;
    input integer gap;
    integer len;
    integer i;
    reg eoc;
    reg [CDATA_BW-1:0] dat;
    begin
        repeat(gap) begin
            @(posedge clk_rd);
        end
        i = 1;
        eoc = 1'b0;
        while(eoc==1'b0) begin
            rd_rdy <=`U_DLY 1'b1;
            @(posedge clk_rd);
            while(rd_vld==1'b0) begin
                @(posedge clk_rd);
            end
            if (i==1) begin
                len = rd_data;
            end else begin
                len = len - 1;
            end
            if (i==2) begin
                dat = rd_data + 1;
            end else if (i>2) begin
                if (dat!=rd_data) begin
                    $error("[ERROR] rd_data is error.");
                end
                dat = rd_data + 1;
            end
            eoc = rd_eoc;
            if(eoc==1'b1 && len!=0) begin
                $error("[ERROR] cell len is error.");
            end
            i = i+1;
        end
        rd_rdy <=`U_DLY 1'b0;
    end
endtask

endmodule

